package net.qiqb.dao.mybatis.plus.event;

import cn.hutool.json.JSONUtil;
import net.qiqb.core.domain.event.DomainEvent;
import net.qiqb.core.domain.event.DomainEventPersistence;
import net.qiqb.core.domain.event.types.DomainEventStatus;
import net.qiqb.spring.ApplicationContextHolder;
import org.springframework.beans.factory.annotation.Autowired;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class DomainEventPersistenceImpl implements DomainEventPersistence {

    private DomainEventDao domainEventDao;

    public DomainEventPersistenceImpl() {

    }

    public void init() {
        if (domainEventDao == null) {
            this.domainEventDao = ApplicationContextHolder.get().getBean(DomainEventDao.class);
        }
    }

    @Override
    public boolean tryBeginSend(String id) {

        return domainEventDao.updateStatusById(id, DomainEventStatus.sending);
    }

    @Override
    public boolean updateSend(String id) {
        return domainEventDao.updateStatusById(id, DomainEventStatus.sent);
    }

    @Override
    public void sent(String id) {
        init();
        domainEventDao.updateStatusById(id, DomainEventStatus.sent);
    }

    @Override
    public void save(List<DomainEvent> events) {
        init();
        domainEventDao.save(events.stream().map(this::formatPO).collect(Collectors.toList()));
    }

    @Override
    public List<DomainEvent> listReady() {
        init();
        final List<DomainEventPO> domainEventPOS = domainEventDao.listReady();
        if (domainEventPOS == null) {
            return new ArrayList<>();
        }
        return domainEventPOS.stream().map(this::formatDO).collect(Collectors.toList());
    }

    private DomainEvent formatDO(DomainEventPO eventPO) {
        final Map params = JSONUtil.toBean(eventPO.getParams(), Map.class);
        try {
            ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(eventPO.getPayload()));
            final Object payload = in.readObject();
            return DomainEvent.load(eventPO.getId(), eventPO.getSource(), eventPO.getCreator(), payload);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }


    private DomainEventPO formatPO(DomainEvent event) {
        Map<String, Object> params = new HashMap<>();
        params.put("payload_class", event.getPayload().getClass().getName());
        params.put("payload", event.getPayload());
        byte[] serializedBytes;

        try {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(event.getPayload());
            oos.flush();
            serializedBytes = bos.toByteArray();
            oos.close();
            bos.close();
            return DomainEventPO.builder()
                    .id(event.getId())
                    .createTime(event.getTimestamp())
                    .params(JSONUtil.toJsonStr(params))
                    .payload(serializedBytes)
                    .source(event.getSource().toString())
                    .creator(event.getCreator())
                    .status(event.getStatus())
                    .build();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    @Autowired
    public void setDomainEventDao(DomainEventDao domainEventDao) {
        this.domainEventDao = domainEventDao;
    }
}
